import {
  Component,
  AfterContentInit,
  ViewEncapsulation,
  ChangeDetectionStrategy,
  Input,
  ElementRef,
  OnChanges,
  SimpleChanges,
  OnDestroy,
} from '@angular/core';

import { InputBoolean, LvGutter, ResizeObserverObject, ResizeObserverService } from '../core/api';

@Component({
  selector: 'lv-group',
  exportAs: 'lvGroup',
  templateUrl: './group.component.html',
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: {
    '[class.lv-group]': 'true',
    '[class.lv-group-row]': 'lvDirection === "horizontal"',
    '[class.lv-group-column]': 'lvDirection === "vertical"',
    '[class.lv-group-row-wrap]': 'lvDirection === "horizontal" && lvWrap === true',
    '[class.lv-group-column-single]': '_isColumns === false && lvWrap === false',
    '[class.lv-group-column-multiple]': '_isColumns === true',
    '[class.lv-group-no-gutter]': 'lvGutter === "none"',
  },
})
export class GroupComponent implements AfterContentInit, OnChanges, OnDestroy {
  // 多列布局-1.设置后开启多列布局模式 2.数组长度为列数 3.数组值为列宽,支持px,%,auto
  @Input() lvColumns: any[] = [];

  // 间距 none | number | string (多列布局模式下此属性失效)
  @Input() lvGutter: LvGutter = 'none';

  // 多列布局-横向间距,不支持%(单列布局模式下此属性失效)
  @Input() lvRowGutter: LvGutter = '8';

  // 多列布局-纵向间距,不支持%(单列布局模式下此属性失效)
  @Input() lvColumnGutter: LvGutter = '8';

  // 是否自动换行，仅在horizontal且lvColumns=[]时有效
  @Input() @InputBoolean() lvWrap: boolean = false;

  // 方向(多列布局模式下此属性失效)
  @Input() lvDirection: 'vertical' | 'horizontal' = 'horizontal';

  _isColumns: boolean;

  // 监听子内容变化
  _contentObserver: MutationObserver;

  private _groupObserver: ResizeObserverObject;
  constructor(
    private elementRef: ElementRef,
    private resizeObserver: ResizeObserverService,
  ) {}

  get formattedLvGutter(): LvGutter {
    return this.formatGutter(this.lvGutter);
  }

  get formattedLvRowGutter(): LvGutter {
    return this.formatGutter(this.lvRowGutter);
  }

  get formattedLvColumnGutter(): LvGutter {
    return this.formatGutter(this.lvColumnGutter);
  }

  formatGutter(gutter: LvGutter): LvGutter {
    if (gutter === 'none' || !gutter) {
      return '0';
    } else if (Number.isNaN(Number(gutter))) {
      // 非数字直接返回
      return gutter;
    }

    return `${gutter}px`;
  }

  ngOnDestroy(): void {
    this._contentObserver?.disconnect();
    this._groupObserver?.unobserve();
  }

  // 清除间距
  clearGutter(node): void {
    if (typeof node.className === 'string') {
      node.className = node.className.replace('lv-group-border-overlap', '');
    }
    node.style.margin = '';
  }

  // 单列布局
  singleColumnLayout(nodes): void {
    const last = nodes.length - 1;

    nodes.forEach((node, i): void => {
      this.clearGutter(node);

      this.setGutter(node, this.lvGutter, this.lvDirection, i === last);
    });
  }

  // 转换gutter为数字
  parseGutterNumber(gutter): any {
    let parsedNumber;
    // 无间距
    if (gutter === 'none' || !gutter) {
      parsedNumber = 0;
    } else {
      // 自定义间距
      parsedNumber = parseFloat(gutter);
    }

    return parsedNumber;
  }

  ngAfterContentInit(): void {
    this.layout();

    // 内容变化时, 重新布局
    this._contentObserver = new MutationObserver((): void => this.layout());
    this._contentObserver.observe(this.elementRef.nativeElement, {
      childList: true,
    });
    this._groupObserver = this.resizeObserver.observe(this.elementRef.nativeElement, (): void => {
      this.layout();
    });
  }

  // 多列布局
  // 1. 计算非auto列的占用宽度 = width + gutter
  // 2. 有auto列,则aoto列平分剩余宽度
  ngOnChanges(changes: SimpleChanges): void {
    const { lvDirection, lvGutter, lvColumns, lvRowGutter, lvColumnGutter } = changes;

    const flag =
      (lvDirection && !lvDirection.firstChange) ||
      (lvGutter && !lvGutter.firstChange) ||
      (lvColumns && !lvColumns.firstChange) ||
      (lvRowGutter && !lvRowGutter.firstChange) ||
      (lvColumnGutter && !lvColumnGutter.firstChange);
    if (flag) {
      this.layout();
    }
  }

  layout(): void {
    const nodes = this.elementRef.nativeElement.childNodes;

    if (!nodes) {
      return;
    }

    // 过滤出ELEMENT_NODE
    const element_nodes = [];
    nodes.forEach((node): void => {
      if (node.nodeType === Node.ELEMENT_NODE && node.nodeName.toLocaleLowerCase() !== 'lv-help-tooltip') {
        element_nodes.push(node);
      }
    });

    if (this.lvColumns.length > 0) {
      this._isColumns = true;
      this.multiColumnLayout(element_nodes);
    } else {
      this._isColumns = false;
      this.singleColumnLayout(element_nodes);
    }
  }

  // 设置间距
  setGutter(node, gutter, direction, isLast = false): void {
    // 无间距
    if (gutter === 'none') {
      if (!this.lvWrap) {
        node.classList.add('lv-group-border-overlap');
      } else {
        // 设置了lvRowGutter，则以该距离配置左右间距
        if (!isLast) {
          node.style.marginRight = this.formattedLvRowGutter;
        }

        // 设置了lvColumnGutter，则以该距离配置上下间距
        node.style.marginTop = this.formattedLvColumnGutter;
        this.elementRef.nativeElement.style.marginTop = `-${this.formattedLvColumnGutter}`;
      }
    } else if (direction === 'horizontal') {
      // 自定义间距
      // 最后一个元素无需设置右侧间距
      if (!isLast) {
        node.style.marginRight = this.formatGutter(gutter);
      }
      // 换行时，所有元素均需设置间距
      if (this.lvWrap) {
        node.style.marginTop = this.formatGutter(gutter);
        this.elementRef.nativeElement.style.marginTop = `-${this.formatGutter(gutter)}`;
      }
    } else {
      // 最后一个元素无需设置间距
      if (!isLast) {
        node.style.marginBottom = this.formatGutter(gutter);
      }
    }
  }

  // 3. 无auto列,则最后一列用marginRight占满剩余宽度
  multiColumnLayout(nodes): void {
    const [columnNumber, rowNumber] = [this.lvColumns.length, Math.ceil(nodes.length / this.lvColumns.length)];

    const [usedWidth, autoWidthNodes, lastColNodes] = [[], {}, []];

    // 1. 计算非auto的占用空间 = width + gutter
    nodes.forEach((node: any, i: number): void => {
      this.clearGutter(node);

      const [rowIndex, colIndex] = [Math.floor(i / columnNumber), i % columnNumber];

      // 列间距
      if (colIndex !== columnNumber - 1) {
        this.setGutter(node, this.lvRowGutter, 'horizontal');
      }

      // 行间距
      if (rowIndex !== rowNumber - 1) {
        this.setGutter(node, this.lvColumnGutter, 'vertical');
      }

      const colWidth = this.lvColumns[colIndex] || 'auto';

      if (colWidth !== 'auto') {
        this.setWidth(node, colWidth);
        // 记录一行的已用宽度
        if (rowIndex === 0) {
          usedWidth.push(colWidth);
        }
      } else {
        if (!autoWidthNodes[colIndex]) {
          autoWidthNodes[colIndex] = [];
        }

        // 自动宽度列
        autoWidthNodes[colIndex].push(node);
      }

      // 最后一列
      if (colIndex === columnNumber - 1) {
        if (this.lvWrap) {
          node.style.marginTop = this.lvRowGutter === 'none' ? this.formattedLvColumnGutter : this.formattedLvRowGutter;
          this.elementRef.nativeElement.style.marginTop =
            this.lvRowGutter === 'none' ? `-${this.formattedLvColumnGutter}` : `-${this.formattedLvRowGutter}`;
        }
        lastColNodes.push(node);
      }
    });

    // 已用宽度
    const usedWidthStr = usedWidth.reduce((memo, width): any => (memo ? `${memo} - ${width}` : width), '') || '0px';

    // 总间距
    const allRowGutter = this.parseGutterNumber(this.lvRowGutter) * (columnNumber - 1);

    // 2. 有auto列,则aoto列平分剩余宽度
    const colIndexs = Object.keys(autoWidthNodes);
    if (colIndexs.length > 0) {
      // 宽度:(100%-总间距-usedWidth)/autoWidthNodes.length
      // IE处理百分比时有0.01px偏差
      const autoWidth = `calc((100% - ${allRowGutter}px - ${usedWidthStr}) /
      ${colIndexs.length} - 0.01px)`;
      colIndexs.forEach((i): void => {
        autoWidthNodes[i].forEach((node): void => {
          this.setWidth(node, autoWidth);
        });
      });
    } else {
      // 3. 无auto列,则最后一列用marginRight占满剩余宽度
      const lastColMarginRight = `calc(100% - ${allRowGutter}px - ${usedWidthStr})`;
      lastColNodes.forEach((node): void => {
        node.style.marginRight = lastColMarginRight;
      });
    }
  }

  // 设置元素宽度为真实宽度减去padding和border值
  private setWidth(node, actualWidth): void {
    const boxSizing = window.getComputedStyle(node).getPropertyValue('box-sizing');
    if (!boxSizing || boxSizing === 'border-box') {
      node.style.width = actualWidth;
    } else {
      node.style.width = `calc(${actualWidth} - ${window.getComputedStyle(node).getPropertyValue('padding-left')} -
          ${window.getComputedStyle(node).getPropertyValue('padding-right')} -
          ${window.getComputedStyle(node).getPropertyValue('border-left-width')} -
          ${window.getComputedStyle(node).getPropertyValue('border-right-width')})`;
    }
  }
}
